package com.roncoo.example.tomcat;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.BitSet;

/**
 * This class is very similar to the java.net.URLEncoder class.
 *
 * Unfortunately, with java.net.URLEncoder there is no way to specify to the java.net.URLEncoder which characters should NOT be encoded.
 *
 * This code was moved from DefaultServlet.java
 *
 * @author Craig R. McClanahan
 * @author Remy Maucherat
 */
public class URLEncoder {

  private static final char[] hexadecimal = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

  public static final URLEncoder DEFAULT = new URLEncoder();

  static {
    DEFAULT.addSafeCharacter('~');
    DEFAULT.addSafeCharacter('-');
    DEFAULT.addSafeCharacter('_');
    DEFAULT.addSafeCharacter('.');
    DEFAULT.addSafeCharacter('*');
    DEFAULT.addSafeCharacter('/');
  }

  // Array containing the safe characters set.
  protected final BitSet safeCharacters = new BitSet(256);

  public URLEncoder() {
    for (char i = 'a'; i <= 'z'; i++) {
      addSafeCharacter(i);
    }
    for (char i = 'A'; i <= 'Z'; i++) {
      addSafeCharacter(i);
    }
    for (char i = '0'; i <= '9'; i++) {
      addSafeCharacter(i);
    }
  }

  public void addSafeCharacter(char c) {
    safeCharacters.set(c);
  }

  /**
   * URL encodes the provided path using UTF-8.
   *
   * @param path The path to encode
   * @return The encoded path
   * @deprecated Use {@link #encode(String, String)}
   */
  @Deprecated
  public String encode(String path) {
    return encode(path, "UTF-8");
  }

  /**
   * URL encodes the provided path using the given encoding.
   *
   * @param path The path to encode
   * @param encoding The encoding to use to convert the path to bytes
   * @return The encoded path
   */
  public String encode(String path, String encoding) {
    int maxBytesPerChar = 10;
    StringBuilder rewrittenPath = new StringBuilder(path.length());
    /***
     * 临时内存区,用以存储需要转换的特殊字符
     */
    ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
    OutputStreamWriter writer = null;
    try {
      writer = new OutputStreamWriter(buf, encoding);
    } catch (Exception e) {
      e.printStackTrace();
      writer = new OutputStreamWriter(buf);
    }

    for (int i = 0; i < path.length(); i++) {
      int c = path.charAt(i);
      if (safeCharacters.get(c)) {
        /***
         * 如果不是特殊字符，直接追加到结果字符串中
         */
        rewrittenPath.append((char) c);
      } else {
        /***
         * 如果是需要转义的，则进行下面的逻辑进行转义。
         */
        // convert to external encoding before hex conversion
        try {
          /***
           * 1.写入临时的缓冲流中.
           */
          writer.write((char) c);
          writer.flush();
        } catch (IOException e) {
          buf.reset();
          continue;
        }
        /***
         * 2.取出
         */
        byte[] ba = buf.toByteArray();
        for (int j = 0; j < ba.length; j++) {
          // Converting each byte in the buffer
          byte toEncode = ba[j];
          rewrittenPath.append('%');
          /***
           * 0xff  为一个字节.
           * byte b=i&0x0f 这个语句的意思是将变量b申明为字节型变量，
           * 将变量i的值高四位清0，保留低四位，赋值给变量b
           */

          /***
           * 高位变0，取低位
           */
          int low = toEncode & 0x0f;
          /***
           * 低位变0，取高位.
           * 右移4
           */
          int high = (toEncode & 0xf0) >> 4;
          rewrittenPath.append(hexadecimal[high]);
          rewrittenPath.append(hexadecimal[low]);
        }
        buf.reset();
      }
    }
    return rewrittenPath.toString();
  }

  /****
   * 移位知识:
   十六进制 f0   对应十进制240   对应二进制11110000
   十六进制0f    对应十进制15    对应二进制00001111
   (ch & f0)>>4     ch与f0按位与，结果并右移4位   取这ch个数的高4位
   ch & 0f              去ch这个数的低4位
   如果你写一个0x80；那么会把这0，1位串在内存中铺开，并且是32位的一个int，不够32位，
   则前面补0。如果在强制转换成一个byte，则会把前面全部截掉，保留后8位。如果是0x80L，
   则是64位的。一次如果你想直接用位来表示数字就可以写成0x的形式。
   */
  public static void main(String[] args) {
    URLEncoder encoder = new URLEncoder();
    /***
     * hello%7E%2A%2D
     */
    System.out.println(encoder.encode("hello~*-", "utf-8"));
  }
}

